#!/usr/bin/env python

# A git pre-push hook that declines commits that don't contain a Reviewed-By:
# tag. The tag must be present on the beginning of the line. To activate, copy
# to $GIT_DIR/hooks/pre-push and make sure the executable flag is on.

# The commit message should also be based on .git-commit-template, although
# that is just best practice and not enforced

import sys
import re
import subprocess


def get_all_commits(ref_from, ref_to):
    args = ['git', 'rev-list', '{:s}..{:s}'.format(ref_from, ref_to)]
    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    return [commit.strip() for commit in out.decode('UTF-8').split('\n') if commit != '']


def commit_message(commit_hash):
    args = ['git', 'cat-file', 'commit', commit_hash]
    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    return out.decode('UTF-8')


def commit_has_rb(commit):
    msg = commit_message(commit)
    for l in msg.split('\n'):
        has_rb = re.search('^Reviewed-by:', l)
        if has_rb:
            return True

    return False


def report_commit(commit_hash):
    print("Commit {:s} does not have Reviewed-By!".format(commit_hash))
    print("Full message:\n======")
    print("{:s}".format(commit_message(commit_hash)))
    print("======")


# man 5 githooks says:
#   Information about what is to be pushed is provided on the hook's
#   standard input with lines of the form:
#       <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
def check_push(hook_input):
    ref_to = hook_input.split()[1][:6]
    ref_from = hook_input.split()[3][:6]
    commit_list = get_all_commits(ref_from, ref_to)

    no_rb_list = []
    for commit in commit_list:
        if not commit_has_rb(commit):
            no_rb_list.append(commit)

    return no_rb_list

# Don't warn when pushing to personal repositories, only origin
remote = sys.argv[1]
if remote != 'origin':
    sys.exit(0)

for hook_input in sys.stdin.readlines():
    no_rb_list = check_push(hook_input)

    if len(no_rb_list) > 0:
        for offender in no_rb_list:
            report_commit(offender)
        sys.exit(1)
